/**************************************************************************
 * Copyright (C) 2017-2017  Unisound
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 **************************************************************************
 *
 * Description : uni_hal_audio_buf.c
 * Author      : wufangfang@unisound.com
 * Date        : 2020.01.08
 *
 **************************************************************************/
#include "uni_hal_audio_buf.h"
#include "uni_iot.h"

typedef struct {
  uni_bool  is_empty;
  int       data_size;
  char      *buf;
  int       buf_size;
}uni_pp_buf_t;

typedef struct {
  uni_pp_buf_t   ping;
  uni_pp_buf_t   pong;
  uni_pp_buf_t   zero;
  uni_pp_buf_t   *active;
  uni_pp_buf_t   *last_active;
}uni_audio_buf_t;

static int _create_pp_buffer(uni_pp_buf_t *buf, int size) {
  buf->is_empty = true;
  buf->data_size = 0;
  buf->buf = (char *)uni_malloc(size);
  if (NULL == buf->buf) {
    printf("pp_buf malloc failed !\r\n");
    return -1;
  }
  uni_memset(buf->buf, 0, size);
  buf->buf_size = size;
  return 0;
}

static void _destroy_pp_buffer(uni_pp_buf_t *buf) {
  if (buf) {
    if (buf->buf) {
      uni_free(buf->buf);
    }
    buf->buf = NULL;
    buf->buf_size = 0;
  }
}

AudioBufHandler uni_audio_buf_create(int size) {
  uni_audio_buf_t *buf = NULL;
  if (size <= 0) {
    return NULL;
  }
  buf = (uni_audio_buf_t *)uni_malloc(sizeof(uni_audio_buf_t));
  if (NULL == buf) {
    printf("audio_buf malloc failed !\r\n");
    return NULL;
  }
  if (0 != _create_pp_buffer(&buf->ping, size)) {
    uni_free(buf);
    return NULL;
  }
  if (0 != _create_pp_buffer(&buf->pong, size)) {
    _destroy_pp_buffer(&buf->ping);
    uni_free(buf);
    return NULL;
  }
  if (0 != _create_pp_buffer(&buf->zero, size)) {
    _destroy_pp_buffer(&buf->pong);
    _destroy_pp_buffer(&buf->ping);
    uni_free(buf);
    return NULL;
  }
  buf->zero.is_empty = false;
  buf->zero.data_size = buf->zero.buf_size;
  buf->active = &buf->zero;
  buf->last_active = buf->active;
  return (AudioBufHandler)buf;
}

void uni_audio_buf_destroy(AudioBufHandler handler) {
  uni_audio_buf_t *audio_buf = (uni_audio_buf_t *)handler;
  if (audio_buf) {
    _destroy_pp_buffer(&audio_buf->zero);
    _destroy_pp_buffer(&audio_buf->pong);
    _destroy_pp_buffer(&audio_buf->ping);
    uni_free(audio_buf);
  }
}

static int _write_data_pp_buf(uni_pp_buf_t *pp_buf,
                                    const char *buf, int size) {
  if (size > pp_buf->buf_size) {
    pp_buf->buf = (char *)uni_realloc(pp_buf->buf, size);
    if (NULL == pp_buf->buf) {
      printf("audio_buf_write malloc failed\r\n");
      return 0;
    }
    pp_buf->buf_size = size;
  }
  uni_memcpy(pp_buf->buf, buf, size);
  pp_buf->data_size = size;
  pp_buf->is_empty = false;
  return size;
}

int uni_audio_buf_write(AudioBufHandler handler,
                              const char *buf, int size) {
  uni_audio_buf_t *audio_buf = (uni_audio_buf_t *)handler;
  if ((NULL == audio_buf) || (NULL == buf) || (size <= 0)) {
    return 0;
  }
  while (1) {
    if (audio_buf->active == &audio_buf->ping) {
      /* DMA reading ping buffer */
      if (!audio_buf->pong.is_empty) {
        /* wait if pong buffer is full */
        uni_msleep(5);
        continue;
      }
      /* write to pong buffer */
      return _write_data_pp_buf(&audio_buf->pong, buf, size);
    } else if (audio_buf->active == &audio_buf->pong) {
      /* DMA reading pong */
      if (!audio_buf->ping.is_empty) {
        /* wait if pong buffer is full */
        uni_msleep(5);
        continue;
      }
      /* write to pong buffer */
      return _write_data_pp_buf(&audio_buf->ping, buf, size);
    } else {
      /* DMA reading zero */
      if (audio_buf->ping.is_empty) {
        return _write_data_pp_buf(&audio_buf->ping, buf, size);
      } else if (audio_buf->pong.is_empty) {
        return _write_data_pp_buf(&audio_buf->pong, buf, size);
      } else {
        uni_msleep(5);
        continue;
      }
    }
    /* should never run to here */
    break;
  }
  /* should never run to here */
  return 0;
}

const char *uni_audio_buf_read(AudioBufHandler handler, int *psize) {
  uni_audio_buf_t *audio_buf = (uni_audio_buf_t *)handler;
  if (NULL == audio_buf) {
    return NULL;
  }
  if (audio_buf->active == &audio_buf->ping) {
    audio_buf->active->is_empty = true;
    /* DMA read ping buffer done, next is pong */
    audio_buf->last_active = audio_buf->active;
    if (audio_buf->pong.is_empty) {
      /* if pong buffer is empty, return zero buffer */
      audio_buf->active = &audio_buf->zero;
    } else {
      audio_buf->active = &audio_buf->pong;
    }
  } else if (audio_buf->active == &audio_buf->pong) {
    audio_buf->active->is_empty = true;
    /* DMA read pong buffer done, next is ping */
    audio_buf->last_active = audio_buf->active;
    if (audio_buf->ping.is_empty) {
      /* if pong buffer is empty, return zero buffer */
      audio_buf->active = &audio_buf->zero;
    } else {
      audio_buf->active = &audio_buf->ping;
    }
  } else {
    /* DMA read zero buffer done, next is ping or pong */
    if (audio_buf->last_active == &audio_buf->ping) {
      /* last active is ping , should get data from pong */
      if (audio_buf->pong.is_empty) {
      /* if pong buffer is empty, return zero buffer */
        audio_buf->active = &audio_buf->zero;
      } else {
        audio_buf->active = &audio_buf->pong;
      }
    } else {
      /* last active is pong or zero, shoud get data from ping at first */
      if (audio_buf->ping.is_empty) {
        /* if pong buffer is empty, return zero buffer */
        audio_buf->active = &audio_buf->zero;
      } else {
        audio_buf->active = &audio_buf->ping;
      }
    }
  }
  *psize = audio_buf->active->data_size;
  return audio_buf->active->buf;
}

int uni_audio_buf_read_size(AudioBufHandler handler) {
  uni_audio_buf_t *audio_buf = (uni_audio_buf_t *)handler;
  if (NULL == audio_buf) {
    return -1;
  }
  return audio_buf->active->data_size;
}

